package filechat;

//<editor-fold defaultstate="collapsed" desc="Imports">
import java.awt.FileDialog;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
//</editor-fold>

public class Connection implements ObjectStreamListener {
    //<editor-fold defaultstate="collapsed" desc="Variables Declaration">
    // Variables declaration
    String clientUsername;
    String contactUsername;
    String connectionIP;
    Socket connectionSocket;
//    ServerSocket fileTransferServerSocket;
    Socket outFileSocket;
    static final int fileTransferPort = 4004;   // several files at the same time?
    String contactData;
    String fileName;
    Long fileSize;
    int bytesRead;
    ObjectOutputStream connectionOutputStream;
    ObjectInputStream connectionInputStream;
    ObjectStreamManager connectionStreamManager;
    ChatWindow chatWindow;
    ChatActionListener actionListener = new ChatActionListener();
    String inMessage;
    String latestMessage;
    String inObject;
    int objectType;
    String time;
    boolean transferring;
    boolean stopped;
    boolean sendingFile;
    boolean receivingFile;
    FileThread fileThread = new FileThread();
    //</editor-fold>

    // Connection constructor
    Connection(Socket socket, int number, String contact, String user, boolean incoming) {
        clientUsername = user;
        contactUsername = contact;
        connectionIP = socket.getInetAddress().getHostAddress();     // Get IP-addess of connected client
        connectionSocket = socket;

        if (incoming) {   // If the connection is incoming, open streams in this order: in, manager, out
            try {
                connectionInputStream = new ObjectInputStream(connectionSocket.getInputStream());      // Start input stream
                connectionStreamManager = new ObjectStreamManager(number, connectionInputStream, this);     // Start manager
                connectionOutputStream = new ObjectOutputStream(connectionSocket.getOutputStream());       // Start output stream
                System.out.println("incoming");
            } catch (IOException e) {
                JOptionPane.showMessageDialog(null,
                        "Connection error: " + e.getMessage(),
                        "Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        } else if (!incoming) {    // If connection is outgoing open in this order: out, in, manager
            try {
                connectionOutputStream = new ObjectOutputStream(connectionSocket.getOutputStream());       // Start output stream
                connectionInputStream = new ObjectInputStream(connectionSocket.getInputStream());      // Start input stream
                connectionStreamManager = new ObjectStreamManager(number, connectionInputStream, this);     // Start manager
            } catch (IOException e) {
                JOptionPane.showMessageDialog(null,
                        "Connection error: " + e.getMessage(),
                        "Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
        startChatWindow();
    } // End of constructor

    //<editor-fold defaultstate="collapsed" desc="startChatWindow">
    // Starts a new ChatWindow and adds listeners
    private void startChatWindow() {
        // Initiate chatWindow
        chatWindow = new ChatWindow();
        chatWindow.setTitle("Chat with: "+contactUsername);
        // Add listeners to chatWindow components
        chatWindow.addWindowListener(new ChatWindowListener());
        chatWindow.writeArea.addKeyListener(new ChatKeyListener());
        chatWindow.sendButton.addActionListener(actionListener);
        chatWindow.clearButton.addActionListener(actionListener);
        chatWindow.sendFileButton.addActionListener(actionListener);
        chatWindow.sendFileMenuItem.addActionListener(actionListener);
        chatWindow.saveConversationMenuItem.addActionListener(actionListener);
        chatWindow.exitMenuItem.addActionListener(actionListener);
        // Make it visible
        chatWindow.setVisible(true);
        chatWindow.setLocationRelativeTo(chatWindow.getParent());
        stopped = false;
        sendingFile = false;
        receivingFile = false;
        fileThread.start();
    } //</editor-fold>
        
    //<editor-fold defaultstate="collapsed" desc="disableChatWindow">
    // Disables most action-related components in the chat window to avoid bugs/errors
    private void disableChatWindow() {
        chatWindow.sendButton.setEnabled(false);
        chatWindow.clearButton.setEnabled(false);
        chatWindow.sendFileButton.setEnabled(false);
        chatWindow.sendFileMenuItem.setEnabled(false);
        chatWindow.writeArea.setEditable(false);
    } //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="objectReceived">
    // Handles incoming objects
    @Override
    public void objectReceived(int number, Object object, Exception exception) {
        if (exception == null) {
            inObject = (String) object;
            objectType = checkObject(object);
            if (objectType == 1) {
                inMessage = inObject.split("\n")[1];
                if (inMessage.equals(latestMessage)) {
                    latestMessage = "";
                } else {
                    chatWindow.readArea.append(inMessage + "\n");                
                    chatWindow.toFront();
                }
            } else if (objectType == 2) {
                // something here? not yet used..
            } else if (objectType == 3) {
                receivingFile = true;
            } else if (objectType == 4) {
                chatWindow.readArea.append(inObject.split("\n")[1]+"\n");
                //disableChatWindow();
            } else {
                System.out.println("Exception...");
            }
        }
    } //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="checkObject">
    // Used to check what kind of object received
    public int checkObject(Object object) {
        try {
            contactData = (String) object;
            return Integer.parseInt(contactData.split("\n")[0]);
        } catch (Exception e) {
            System.out.println("checkObject exception: " + e.getMessage());
            return 0;
        }
    } //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="sendMessage">
    // Tries to write an object (from the write area) on the ObjectOutputStream
    void sendMessage(boolean sendButton) {
        time = (Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ":" + Calendar.getInstance().get(Calendar.MINUTE));
        try {
            if (!chatWindow.writeArea.getText().equals("")) {
                latestMessage = ("(" + time + ") " + clientUsername + ": " + chatWindow.writeArea.getText()).split("\n")[0];
                connectionOutputStream.writeObject(1 + "\n" + "(" + time + ") " + clientUsername + ": " + chatWindow.writeArea.getText());
                connectionOutputStream.flush();
                // Adds a row break if the sendMessage() method is called from the send-button
                if (sendButton) {
                    chatWindow.readArea.append("(" + time + ") " + clientUsername + ": " + chatWindow.writeArea.getText() + "\n");
                } else {
                    chatWindow.readArea.append("(" + time + ") " + clientUsername + ": " + chatWindow.writeArea.getText());
                }
                chatWindow.writeArea.setText("");
            }
        } catch (IOException e) {
            chatWindow.readArea.append("(" + time + ") " + clientUsername + ": " + chatWindow.writeArea.getText());
            chatWindow.readArea.append("(" + time + ") " + "Niggah u stoopid? GIT!" + "\n");
            chatWindow.writeArea.setText("");
        }
    } //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="closeConnection">
    // Attempts to close a connection (socket, manager and I/O streams)
    void closeConnection() {
        if (!connectionSocket.isClosed()) {
            try {
                connectionSocket.close();
                connectionStreamManager.closeManager();
                connectionInputStream.close();
                connectionOutputStream.close();
                stopped = true;
            } catch (IOException e) {
                // Error handling needed?
            }        
        }
    } //</editor-fold>

// <editor-fold defaultstate="collapsed" desc="INNER/LISTENER CLASSES">
//*********************** INNER CLASSES / LISTENERS **************************\\
    // Listens for keyEvents
    private class ChatKeyListener extends KeyAdapter {

        @Override
        public void keyTyped(KeyEvent e) {
            if (e.getKeyChar() == KeyEvent.VK_ENTER) {
                sendMessage(false);
            }
        }
//        @Override
//        public void keyPressed(KeyEvent ev) {
//            if(ev.getKeyCode() == KeyEvent.VK_ENTER && ev.isShiftDown()) {
//                chatWindow.writeArea.append("\n");
//            } else if (ev.getKeyChar() == KeyEvent.VK_ENTER) {
//                sendMessage(false);
//            } 
//            
//        }
    }
    // Listens for windowClosing events
    private class ChatWindowListener extends WindowAdapter {

        @Override
        public void windowClosing(WindowEvent e) {
            try {
                connectionOutputStream.writeObject(4 + "\n" + clientUsername + " has disconnected!");
                connectionOutputStream.flush();
            } catch (IOException ex) {}
            closeConnection();
            e.getWindow().dispose();
        }
    }
    // Listens for action events
    private class ChatActionListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            // Calls sendMessage() method
            if (e.getSource().equals(chatWindow.sendButton)) {
                sendMessage(true);
            } // Clears all text from the write area 
            else if (e.getSource().equals(chatWindow.clearButton)) {
                chatWindow.writeArea.setText("");
            } // Sets the boolean sendingFile to true --> FileThread sends the file
            else if (e.getSource().equals(chatWindow.sendFileMenuItem) || e.getSource().equals(chatWindow.sendFileButton)) {
                try {
                    connectionOutputStream.writeObject(3 + "\n");
                    connectionOutputStream.flush();
                } catch (IOException ex) {
                    System.out.println("IOerror when requesting to send a file");
                    return;
                }
                sendingFile = true;
            } // Opens a file chooser for saving the conversation
            else if (e.getSource().equals(chatWindow.saveConversationMenuItem)) {
                JFileChooser chooser = new JFileChooser();
                int returnVal = chooser.showSaveDialog(null);

                if (returnVal == JFileChooser.APPROVE_OPTION) {
                    File savefile = chooser.getSelectedFile();
                    saveConversation(savefile);
                }
            } // Writes on the stream, then disconnects och disposes of the window
            else if (e.getSource().equals(chatWindow.exitMenuItem)) {
                try {
                    connectionOutputStream.writeObject(4 + "\n" + clientUsername + " has disconnected" + "\n");
                    connectionOutputStream.flush();
                } catch (IOException ex) {}
                closeConnection();
                chatWindow.dispose();
            }
        }
        // (tries to)Write the conversation in the chatWindow to a file. (FIX SO THAT ROW-BREAKS ARE INCLUDED!?)
        void saveConversation(File file) {
            try {
                FileWriter fstream = new FileWriter(file);
                BufferedWriter out = new BufferedWriter(fstream);
                out.write(chatWindow.readArea.getText());
                out.close();
            } catch (Exception e) {
                System.err.println("save file Error: " + e.getMessage());
            }
        }
    }
    // Handles incoming files
    class FileThread extends Thread {
        //JProgressBar progressBar;

//        private ProgressThread progressWorker;
//        private JProgressBar progressBar;
        @Override
        public void run() {
            while (!stopped) {
                if (receivingFile) {
                    receiveFile();
                }
                if (sendingFile) {
                    int returnVal = chatWindow.chatWindowFileChooser.showOpenDialog(chatWindow);
                    if (returnVal == JFileChooser.APPROVE_OPTION) {                       
                        sendFile(chatWindow.chatWindowFileChooser.getSelectedFile());
                    } else {
                        sendingFile = false;
                        System.out.println("FileChooser closed!");
                    }
                }
            }
        }
        //<editor-fold defaultstate="collapsed" desc="receiveFile">
        /*
         * Waits on a file and saves it to a chosen destination with a chosen name.
         */
        public void receiveFile() {
            time = (Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ":" + Calendar.getInstance().get(Calendar.MINUTE));
            chatWindow.sendFileButton.setEnabled(false);
            chatWindow.sendFileMenuItem.setEnabled(false);
            try {
                System.out.println("Waiting on file");
                ServerSocket fileTransferServerSocket = new ServerSocket(fileTransferPort);
                System.out.println("incoming file socket created");
                Socket inFileSocket = fileTransferServerSocket.accept();
                System.out.println("file connect accepted");

//                progressWorker = new ProgressThread();
//                progressWorker.addPropertyChangeListener(fileThread);
//                progressWorker.execute();

                DataInputStream dis = new DataInputStream(inFileSocket.getInputStream());
                fileSize = dis.readLong();
                fileName = dis.readUTF();
                chatWindow.readArea.append("(" + time + ") " + "Incoming file, size: " + (long) fileSize / 1024 + " KB\n");
                FileDialog choose = new FileDialog(chatWindow, "Choose File Destination", FileDialog.SAVE);
                choose.setDirectory(null);
                choose.setFile(fileName);
                choose.setVisible(true);

//                transferring = true;

                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(choose.getDirectory() + choose.getFile()));

                //int bytesRead = dis.read(inFileByteArray, 0, inFileByteArray.length);
                //bos.write(inFileByteArray, 0, bytesRead);

                byte[] inFileByteArray = new byte[1024];
                while (fileSize > 0 && (bytesRead = dis.read(inFileByteArray, 0, (int) Math.min(inFileByteArray.length, fileSize))) != -1) {
                    bos.write(inFileByteArray, 0, bytesRead);
                    fileSize -= bytesRead;
                }
                bos.close();
                inFileSocket.close();
                fileTransferServerSocket.close();
            } catch (IOException ex) {
                chatWindow.readArea.append("(" + time + ") " + "ERROR! I/O Excpetion while receiving file: " + ex.getMessage() + "\n");
//                if(!fileTransferServerSocket.isClosed()) {
//                    try {
//                        fileTransferServerSocket.close();
//                    } catch (IOException ex1) {
//                        System.out.println("closeFTsocket error");
//                    }
//                }
                receivingFile = false;
                chatWindow.sendFileButton.setEnabled(true);
                chatWindow.sendFileMenuItem.setEnabled(true);
                return;
            }
            System.out.println("File received");
            chatWindow.readArea.append("(" + time + ")" + ":File received\n");
            receivingFile = false;
            chatWindow.sendFileButton.setEnabled(true);
            chatWindow.sendFileMenuItem.setEnabled(true);
        } //</editor-fold> 
        
        //<editor-fold defaultstate="collapsed" desc="sendFile">
        /*
         * Sets up a separate socket connection to the contact and writes the file
         * as a byte array on the outputstream. When the file is sent this connection is closed.
         */
        void sendFile(File outFile) {
            chatWindow.sendFileButton.setEnabled(false);
            chatWindow.sendFileMenuItem.setEnabled(false);
            time = (Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ":" + Calendar.getInstance().get(Calendar.MINUTE));
            chatWindow.readArea.append("(" + time + ")" + " Attempting to send file\n");
            System.out.println(connectionIP);
            System.out.println(fileTransferPort);
            try {
                outFileSocket = new Socket(connectionIP, fileTransferPort);
                System.out.println("Trying to send file");
                DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(outFile)));
                DataOutputStream dos = new DataOutputStream(outFileSocket.getOutputStream());

                byte[] bufferedBytes = new byte[(int) 1024];
                int bytesRead = 0;
                dos.writeLong(outFile.length());
                dos.writeUTF(outFile.getName());
                while (-1 != (bytesRead = dis.read(bufferedBytes, 0, bufferedBytes.length))) {
                    dos.write(bufferedBytes, 0, bytesRead);
                }
                
                dos.flush();
                outFileSocket.close();
            } catch (FileNotFoundException e) {
                if (!outFileSocket.isClosed()) { try { outFileSocket.close(); } catch (IOException ex) {} }
                chatWindow.readArea.append("(" + time + ") " + "ERROR! File not found\n");
                sendingFile = false;
                chatWindow.sendFileButton.setEnabled(true);
                chatWindow.sendFileMenuItem.setEnabled(true);
                return;
            } catch (IOException e) {
                if (!outFileSocket.isClosed()) { try { outFileSocket.close(); } catch (IOException ex) {} }
                chatWindow.readArea.append("(" + time + ") " + "ERROR! I/O Excpetion while sending file: " + e.getMessage() + "\n");
                sendingFile = false;
                chatWindow.sendFileButton.setEnabled(true);
                chatWindow.sendFileMenuItem.setEnabled(true);
                return;
            }
            sendingFile = false;
            System.out.println("File sent");
            chatWindow.readArea.append("(" + time + ")" + ": File sent\n");
            chatWindow.sendFileButton.setEnabled(true);
            chatWindow.sendFileMenuItem.setEnabled(true);
        } // </editor-fold>
        
//        @Override
//        public void propertyChange(PropertyChangeEvent pce) {
//            System.out.println("property name?");
//            System.out.println(pce.getPropertyName());
//            if ("progress".equals(pce.getPropertyName())) {
//                System.out.println("Changing property");
//                int progress = (Integer) pce.getNewValue();
//                System.out.println("progress");
//                System.out.println(progress);
//                progressBar.setValue(progress);
//            }
//        }
//        class ProgressThread extends SwingWorker<Void, Void> {
//
//            @Override
//            public Void doInBackground() {
//                System.out.println("In ProgressThread");
//                JPanel progressPanel = new JPanel();
//                JFrame progressFrame = new JFrame();
//                System.out.println("filesizes intvalue");
//                System.out.println(fileSize.intValue());
//                progressBar = new JProgressBar(0, fileSize.intValue());
//                progressBar.setStringPainted(true);
//                //progressBar.addChangeListener(null);
//                progressPanel.add(progressBar);
//                progressFrame.setContentPane(progressPanel);
//                progressFrame.pack();
//                progressFrame.setVisible(true);
//                int progress = 0;
//                setProgress(0);
//                while (transferring) {
//                    System.out.println("transferring");
//                    progress = fileSize.intValue();
//                    setProgress(Math.min(progress, fileSize.intValue()));
//                }
//                System.out.println("ProgressThread done");
//                return null;
//            }
//        }
    }
    // </editor-fold>
} // End of Connection
